using System;
using System.Collections;
using System.Runtime.Serialization;
using System.Collections.Generic;
using Evaluant.Uss;
using Evaluant.Uss.Collections;
using System.Diagnostics;

namespace Evaluant.Uss.ObjectContext
{
	internal sealed class CollectionDebugView
	{

		private IEnumerable _collection;

		public CollectionDebugView(IEnumerable collection)
		{
			if (collection == null)
			{
				throw new ArgumentNullException("collection");
			}
			_collection = collection;
		}

		[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
		public object[] Items
		{
			get
			{
				ArrayList list = new ArrayList();
				foreach (object o in _collection)
					list.Add(o);
				return list.ToArray();
			}
		}
	}

	[Serializable()]
	[DebuggerDisplay("Count = {Count}")]
	[DebuggerTypeProxy(typeof(CollectionDebugView))]
	public class IPersistableCollection<T> : IList<T>, ISerializable, IList
			where T : class
	{
		#region Members

		private IList _List;
		protected ObjectContext _PersistenceManager;
		protected IPersistable _Parent;
		protected string _Role;

		#endregion

		#region Ctor

		public IPersistableCollection()
		{
			_List = new HashedList();
		}

		public IPersistableCollection(ObjectContext persistenceManager, IPersistable parent, string role, IEnumerable<T> data)
		{
			_PersistenceManager = persistenceManager;
			_Parent = parent;
			_Role = role;

			_List = new HashedList();
			if (data != null)
			{
				foreach (T o in data)
					_List.Add(o);
			}
			else
			{
				// create by dynamicProxy, fill the collection if already loaded
				if (parent.Entity.InferredReferences.Contains(role) && _PersistenceManager != null)
				{
					EntitySet entities = parent.Entity.GetEntitySet(role);
					IList items = _PersistenceManager.LoadWithEntities<T>(entities);
					foreach (T o in items)
						_List.Add(o);
				}
			}
		}

		public IPersistableCollection(ICollection<T> value)
		{
			this.AddRange(value);
		}

		#endregion

		public bool IsAttached
		{
			get { return _Parent != null && _Parent.Entity != null; }
		}

		internal bool IsLoaded()
		{
			if (IsAttached)
				return _Parent.Entity.InferredReferences.Contains(_Role);

			return _List != null;
		}

		internal void EnsureLoaded()
		{
			if (IsLoaded() || (ObjectContext != null && !ObjectContext.EnableLazyLoading))
				return;

			if (_PersistenceManager != null && IsAttached)
			{
				_List = _PersistenceManager.LoadReference(_Parent.Entity, _Role);
			}
			else // Standalone collection
			{
				_List = new HashedList();
			}
		}

		public ObjectContext ObjectContext
		{
			get { return _PersistenceManager; }
			set { _PersistenceManager = value; }
		}

		public string Role
		{
			get { return _Role; }
			set { _Role = value; }
		}

		public T this[int index]
		{
			get
			{
				EnsureLoaded();
				return ((T)(_List[index]));
			}
			set
			{
				EnsureLoaded();
				if (IsAttached)
				{
					_Parent.Entity.DeleteElement(_Role, ((IPersistable)_List[index]).Entity);
					_Parent.Entity.AddValue(_Role, GetEntity(value));
				}

				_List[index] = value;
			}
		}

		private Entity GetEntity(object item)
		{
			IPersistable persistable = item as IPersistable;

			if (persistable != null)
				return persistable.Entity;
			else
				return _PersistenceManager.ExtractEntity(item);
		}

		public void Add(T item)
		{
			AddItem(item);
		}

		int AddItem(T item)
		{
			EnsureLoaded();
			// if the collection is not autonomous, add the added entity to the parent's role
			if (IsAttached)
			{
				_Parent.Entity.AddValue(_Role, GetEntity(item));

				// mark reference as "loaded"
				if (!_Parent.Entity.InferredReferences.Contains(_Role))
					_Parent.Entity.InferredReferences.Add(_Role);
			}

			return _List.Add(item);
		}

		public void AddRange(IEnumerable<T> value)
		{
			EnsureLoaded();
			foreach (T item in value)
			{
				this.Add(item);
			}
		}

		public bool Contains(T value)
		{
			EnsureLoaded();
			return _List.Contains(value);
		}

		public void CopyTo(T[] array, int index)
		{
			EnsureLoaded();
			_List.CopyTo(array, index);
		}

		public int IndexOf(T value)
		{
			EnsureLoaded();
			return _List.IndexOf(value);
		}

		public void Clear()
		{
			EnsureLoaded();

			if (IsAttached)
				_Parent.Entity.DeleteElement(_Role);

			_List.Clear();
		}

		public void Insert(int index, T value)
		{
			EnsureLoaded();
			_List.Insert(index, value);
		}

		public int Count
		{
			get
			{
				EnsureLoaded();
				return _List.Count;
			}
		}

		public void RemoveAt(int index)
		{
			EnsureLoaded();
			_List.RemoveAt(index);
		}

		public bool Remove(T item)
		{
			EnsureLoaded();
			if (IsAttached)
			{
				_Parent.Entity.DeleteElement(_Role, GetEntity(item));

				// mark reference as "loaded"
				if (!_Parent.Entity.InferredReferences.Contains(_Role))
					_Parent.Entity.InferredReferences.Add(_Role);
			}
			_List.Remove(item);

			return true;
		}

		public IEnumerator<T> GetEnumerator()
		{
			EnsureLoaded();
			return new IEnumeratorWrapper<T>(_List.GetEnumerator());
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			EnsureLoaded();
			return _List.GetEnumerator();
		}


		public bool IsSynchronized
		{
			get
			{
				EnsureLoaded();
				return _List.IsSynchronized;
			}
		}

		public object SyncRoot
		{
			get
			{
				EnsureLoaded();
				return _List.SyncRoot;
			}
		}

		public bool IsFixedSize
		{
			get
			{
				EnsureLoaded();
				return _List.IsFixedSize;
			}
		}

		public bool IsReadOnly
		{
			get
			{
				EnsureLoaded();
				return _List.IsReadOnly;
			}
		}

		#region ISerialiazable implementation

		void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
		{
			info.SetType(typeof(List<T>));

			List<T> list = new List<T>();

			if (_List != null)
				foreach (T item in _List)
					list.Add(item);

			System.Reflection.MemberInfo[] memberInfos = FormatterServices.GetSerializableMembers(typeof(List<T>));
			object[] memberValues = FormatterServices.GetObjectData(list, memberInfos);

			for (int i = 0; i < memberInfos.Length; i++)
				info.AddValue(memberInfos[i].Name, memberValues[i]);
		}

		#endregion

		private class IEnumeratorWrapper<Y> : IEnumerator<Y>
		{
			private IEnumerator _Enumerator;

			public IEnumeratorWrapper(IEnumerator enumerator)
			{
				_Enumerator = enumerator;
			}

			#region IEnumerator<Y> Members

			public Y Current
			{
				get { return (Y)_Enumerator.Current; }
			}

			#endregion

			#region IDisposable Members

			public void Dispose()
			{
				_Enumerator = null;
			}

			#endregion

			#region IEnumerator Members

			object IEnumerator.Current
			{
				get { return _Enumerator.Current; }
			}

			public bool MoveNext()
			{
				return _Enumerator.MoveNext();
			}

			public void Reset()
			{
				_Enumerator.Reset();
			}

			#endregion
		}

		#region IList Members

		int IList.Add(object value)
		{
			return AddItem((T)value);
		}

		void IList.Clear()
		{
			Clear();
		}

		bool IList.Contains(object value)
		{
			return Contains((T)value);
		}

		int IList.IndexOf(object value)
		{
			return IndexOf((T)value);
		}

		void IList.Insert(int index, object value)
		{
			Insert(index, (T)value);
		}

		bool IList.IsFixedSize
		{
			get { return IsFixedSize; }
		}

		bool IList.IsReadOnly
		{
			get { return IsReadOnly; }
		}

		void IList.Remove(object value)
		{
			Remove((T)value);
		}

		void IList.RemoveAt(int index)
		{
			RemoveAt(index);
		}

		object IList.this[int index]
		{
			get
			{
				return this[index];
			}
			set
			{
				this[index] = (T)value;
			}
		}

		#endregion

		#region ICollection Members

		void ICollection.CopyTo(Array array, int index)
		{
			EnsureLoaded();
			_List.CopyTo(array, index);
		}

		int ICollection.Count
		{
			get { return Count; }
		}

		bool ICollection.IsSynchronized
		{
			get { return IsSynchronized; }
		}

		object ICollection.SyncRoot
		{
			get { return SyncRoot; }
		}

		#endregion
	}
}
